import Ref from "./Ref";

/**
 * @template K
 * @template V
 * @extends { Ref<Map<K, V>> }
 */
export default class RefMap extends Ref {

  size;

  /** @type { { key: K, func: (value: V, oldValue: V) => void; deep?: boolean; }[] } */
  fieldEvents = [];

  constructor() {
    super(new Map());
    this.size = new Ref(this.save.size);
  }

  clear() {
    const keys = Array.from(this.save.keys());
    const values = Array.from(this.save.values());
    this.save.clear();
    this.size.value = this.save.size;

    this.fieldEvents.forEach(info => {
      const key = info.key;
      const index = keys.indexOf(key);
      if (index !== -1) {
        const newValue = this.save.get(key);
        const oldValue = values[index];
        if (newValue !== oldValue || info.deep) {
          info.func(newValue, oldValue);
        }
      }
    });
  }

  /**
   * @param { K } key
   * @return { boolean }
   */
  delete(key) {
    const oldValue = this.save.get(key);
    const result = this.save.delete(key);
    this.size.value = this.save.size;
    this.refreshByKey(key, this.save.get(key), oldValue);
    return result;
  }

  /**
   * @param { K } key
   * @param { V } value
   */
  set(key, value) {
    const oldValue = this.save.get(key);
    this.save.set(key, value);
    this.size.value = this.save.size;
    this.refreshByKey(key, value, oldValue);
  }

  /**
   * @param { K } key
   * @param { V } newValue
   * @param { V } oldValue
   */
  refreshByKey(key, newValue, oldValue) {
    this.size.value = this.save.size;

    this.fieldEvents.filter((info) => info.key === key).forEach(info => {
      if (newValue !== oldValue || info.deep) {
        info.func(newValue, oldValue);
      }
    });
  }

  /**
   * @param { K } key
   * @param { boolean } [deep]
   * @return { Ref<boolean> }
   */
  has(key, deep) {
    const ret = new Ref(this.save.has(key));
    this.fieldEvents.push({ key, func: () => { ret.value = this.save.has(key); }, deep });
    ret.bind((newValue, oldValue) => { this.refreshByKey(key, newValue, oldValue); }, true);
    return ret;
  }

  /**
   * @param { K } key
   * @param { boolean } [deep]
   * @return { Ref<V> }
   */
  get(key, deep) {
    const ret = new Ref(this.save.get(key));
    this.fieldEvents.push({ key, func: () => { ret.value = this.save.get(key); }, deep });
    ret.bind((newValue, oldValue) => { this.refreshByKey(key, newValue, oldValue); }, true);
    return ret;
  }

  /**
   * @param { K } key
   * @param { boolean } [deep]
   * @return { V extends Ref<any> ? V : any }
   */
  getValue(key, deep) {
    const retValue = this.save.get(key);
    if (!(retValue instanceof Ref)) {
      throw new Error("variable is not Ref type");
    }
    const ret = new Ref(retValue.value);
    this.fieldEvents.push({ key, func: () => { ret.value = this.save.get(key); }, deep });
    ret.bind((newValue, oldValue) => { this.refreshByKey(key, newValue, oldValue); }, true);
    retValue.bind((newValue, oldValue) => { this.refreshByKey(key, newValue, oldValue); }, true);
    // @ts-ignore
    return ret;
  }

}
